package spring.dao.support;

import mybatis.exception.MyBatisException;
import mybatis.session.Configuration;
import mybatis.session.SqlSession;
import mybatis.session.SqlSessionFactory;
import mybatis.session.TransactionType;
import spring.transaction.TransactionThreadManager;
import utils.LogUtils;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;
import java.util.Objects;

public class SqlSessionTemplate implements SqlSession, DisposableBean {

    private final SqlSessionFactory sqlSessionFactory;
    private final SqlSession sqlSessionProxy;

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
        sqlSessionFactory.getConfiguration().setTransactionType(TransactionType.SPRING);
        this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(),
                new Class[]{SqlSession.class}, new SqlSessionInterceptor(sqlSessionFactory));
        System.out.println(sqlSessionProxy);
    }

    public SqlSessionFactory getSqlSessionFactory() {
        return this.sqlSessionFactory;
    }

    @Override
    public <T> T selectOne(String statement, Object... parameters) {
        return this.sqlSessionProxy.selectOne(statement, parameters);
    }

    @Override
    public <E> List<E> selectList(String statement, Object... parameters) {
        return this.sqlSessionProxy.selectList(statement, parameters);
    }

    @Override
    public int insert(String statement, Object parameter) {
        return this.sqlSessionProxy.insert(statement, parameter);
    }

    @Override
    public int update(String statement, Object parameter) {
        return this.sqlSessionProxy.update(statement, parameter);
    }

    @Override
    public int delete(String statement, Object parameter) {
        return this.sqlSessionProxy.delete(statement, parameter);
    }

    @Override
    public <T> T getMapper(Class<T> mapperClass) {
        return this.sqlSessionProxy.getMapper(mapperClass);
    }

    @Override
    public void commit() {
        throw new UnsupportedOperationException("不允许在Spring管理的SqlSession上手动提交事务。");
    }

    @Override
    public void commit(boolean force) {
        throw new UnsupportedOperationException("不允许在Spring管理的SqlSession上手动提交事务。");
    }

    @Override
    public void rollback() {
        throw new UnsupportedOperationException("不允许在Spring管理的SqlSession上手动回滚事务。");
    }

    @Override
    public void rollback(boolean force) {
        throw new UnsupportedOperationException("不允许在Spring管理的SqlSession上手动回滚事务。");
    }

    @Override
    public void close() {
        throw new UnsupportedOperationException("不允许在Spring管理的SqlSession上手动关闭会话。");
    }

    @Override
    public void destroy() {
    }

    public Configuration getConfiguration() {
        return this.sqlSessionFactory.getConfiguration();
    }


    private static class SqlSessionInterceptor implements InvocationHandler {

        private final SqlSessionFactory sqlSessionFactory;

        public SqlSessionInterceptor(SqlSessionFactory sqlSessionFactory) {
            this.sqlSessionFactory = sqlSessionFactory;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (!TransactionThreadManager.hasResource(sqlSessionFactory)) {
                TransactionThreadManager.bindResource(sqlSessionFactory, sqlSessionFactory.openSession());
            }
            SqlSession sqlSession = (SqlSession) TransactionThreadManager.getResource(sqlSessionFactory);
            if (Objects.isNull(sqlSession)) {
                throw new MyBatisException("SQL会话无效。");
            }
            try {
                Object result = method.invoke(sqlSession, args);
                if (!isSqlSessionTransactional(sqlSession, sqlSessionFactory)) {
                    sqlSession.commit(true);
                }
                return result;
            } catch (Throwable throwable) {
                closeSqlSession(sqlSession, sqlSessionFactory);
                sqlSession = null;
                throw throwable;
            } finally {
                if (sqlSession != null) {
                    closeSqlSession(sqlSession, sqlSessionFactory);
                }
            }
        }

        public boolean isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory) {
            Object sqlSession = TransactionThreadManager.getResource(sessionFactory);
            return sqlSession == session;
        }

        public void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
            SqlSession sqlSession = (SqlSession) TransactionThreadManager.getResource(sessionFactory);
            if ((sqlSession != null) && (sqlSession != session)) {
                LogUtils.debug("SqlSession [" + session + "] 执行结束被关闭。");
                session.close();
            }
        }
    }


}
